package com.ayg.test.apitest.core;

import com.ayg.test.apitest.constants.*;
import com.ayg.test.apitest.function.RegexFunction;
import com.ayg.payment.util.DigestUtil;
import com.ayg.test.apitest.utils.StringUtil;
import com.ayg.test.apitest.utils.TestRSADigestUtil;
import com.google.gson.Gson;
import com.ayg.test.apitest.entity.TestCase;
import  com.ayg.test.apitest.entity.Validate;
import io.restassured.response.Response;
import io.restassured.response.ValidatableResponse;
import io.restassured.specification.RequestSpecification;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.ITestContext;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.testng.Reporter;

import java.io.File;
import java.io.IOException;
import java.util.*;

import static io.restassured.RestAssured.given;
import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath;
import static org.hamcrest.Matchers.*;

public class ApiEngine extends BaseEngine {
    private static final Logger logger = LoggerFactory.getLogger(ApiEngine.class);
    private static final String ENCODING = "UTF-8";
    private static final String TRUE = "true";

    @DataProvider(name = "apiDatas")
    public Iterator<Object[]> getApiData(ITestContext context){
        List<Object[]> dataProvider = new ArrayList<>();
        for (TestCase data : testCases) {
            dataProvider.add(new Object[] { data });
        }
        return dataProvider.iterator();
    }

    @Test(dataProvider = "apiDatas")
    public void apiTest(TestCase testCase) throws Exception {
        if(testCase.getBeforeAction() != null && !testCase.getBeforeAction().isEmpty()){
            logger.info("beforeAction:{},",testCase.getBeforeAction().size());
            List<Map<String,String>> beforeActionList = testCase.getBeforeAction();
            for(Map<String,String> beforeItem : beforeActionList){
                handleActions(beforeItem);
            }

        }
        logger.info("测试用例类型：{}", project);
        Gson gson = new Gson();
        logger.info("执行测试用例：:{}" ,testCase.getName());
        Response response = null;
        String method = testCase.getRequest().getMethod();
        Map requests = testCase.getRequest().getJson();
        //将参数放入TreeMap，方便签名排序
        Map requestParams = new TreeMap();
        requestParams.putAll(requests);
        Map headers = testCase.getRequest().getHeaders();
        logger.info("预处理前请求参数：{}",gson.toJson(requestParams));
        //请求前进行参数替换${} ,和预处理函数__fucn()
        replacePlaceholder(headers);
        replacePlaceholder(testCase.getRequest().getCookies());
        replacePlaceholder(requestParams);
        logger.info("请求url：{}",testCase.getRequest().getUrl());
        logger.info("请求method：{}",method);
        logger.info("请求参数：{}",gson.toJson(requestParams));
        //设置请求参数，并发送请求
        RequestSpecification rs = given();//.proxy("127.0.0.1",8081).relaxedHTTPSValidation();

        for (Object key : testCase.getRequest().getCookies().keySet()) {
            if(!cookies.containsKey(key)){
                cookies.put(key,testCase.getRequest().getCookies().get(key));
            }
        }
        if(cookies != null && cookies.size()>0){
            logger.info("设置请求cookies:{}",cookies);
            rs.cookies(cookies);
        }
        String contentType = (String)testCase.getRequest().getHeaders().get("Content-Type");
        rs.contentType(contentType);
        if(headers!=null){
            rs.headers(headers);
        }
        if (RequestConstants.GET.equalsIgnoreCase(method)) {
            if(requestParams!=null){
                rs.params(requestParams);
            }
            response = rs.get( testCase.getRequest().getUrl());
        } else if (RequestConstants.POST.equalsIgnoreCase(method)) {
            //文件上传
            if("multipart/form-data".equals(contentType)){
                doFileUpload(gson, requestParams, rs);
            }else{
                if(requestParams!=null){
                    //判断是否要加密
                    digest(requestParams);
                    if("query".equals(testCase.getRequest().getParamType())){
                        rs.queryParams(requestParams);
                    }else {
                        rs.body(gson.toJson(requestParams));
                    }
                    //   rs.body(requestParams);
                    //   rs.queryParams(requestParams);
                    Reporter.log(gson.toJson(requestParams));
                }
            }
            response = rs.post(testCase.getRequest().getUrl());
           /* response = given().contentType(ContentType.JSON)
                    .headers(headers)
                    .body(requestParams)
                    //.params(requestParams)
                    .post(testCase.getRequest().getUrl());
                    */
        }
        if(response == null){
            return;
        }
        ValidatableResponse resp = response.then();
        List<Validate> validateList = testCase.getValidate();
        logger.info("请求返回数据：{}",response.asString());
        Reporter.log(response.asString());
        if("true".equals(saveToDB)&&"payment".equals(project)) {
            //请求后校验前写入数据表
            //将API用例写入数据库为第二步校验做准备
            logger.info("支付接口测试，需要将数据写数据表进行第二步校验操作");
            String resultMessage = response.getBody().jsonPath().getString("resultMessage");
            int status = CasesState.DEFAULT;
            int id = dao.insert(SqlString.INSERT_PAYMENT_TESTCASE, testCase.getName()
                    , gson.toJson(testCase.getRequest())
                    , gson.toJson(testCase.getValidate())
                    , response.getBody().jsonPath().getString("requestId")
                    , status
                    , resultMessage
                    , env
                    , testCase.getType());
            testCase.setId(id);
        }
        //检查点校验
        validate(response, resp, validateList);
        //默认保存上次请求的cookies
        cookies.putAll(response.getCookies());
        logger.info("response cookies {}",response.getCookies());
        logger.info("response cookies {}",cookies);
        //用例执行成功进行提取参数处理
        doExtractParam(testCase, response);
        if(testCase.getAfterAction() != null && !testCase.getAfterAction().isEmpty()){
            logger.info("afterAction:{},",testCase.getAfterAction().size());
            List<Map<String,String>> afterActionList =  testCase.getAfterAction();
            for(Map<String,String> afterItem : afterActionList){
                handleActions(afterItem);
            }
        }
    }

    private void handleActions(Map<String, String> beforeItem) throws InterruptedException {
        String sql = beforeItem.get("sql");
        String prefix = beforeItem.get("prefix");
        if("wait".equals(beforeItem.get("type"))){
            long time = Long.parseLong(beforeItem.get("time"));
            logger.info("wait for min:{}",time);
            Thread.sleep(time);
        }else if("select".equals(beforeItem.get("type"))) {
            logger.info("sql:{}", beforeItem.get("sql"));
            Map<String,String> prepareDataMap = prepareDataDao.queryPrepareData(env,sql);
            prepareDataMap.forEach((key,value) ->{
                key = prefix+"."+key;
                saveDatas.put(key,value);
            });
        }else if("update".equals(beforeItem.get("type"))){
            prepareDataDao.update(env,sql);
        }
    }

    private void doExtractParam(TestCase testCase, Response response) {
        List extractList = testCase.getExtract();
        if (extractList != null) {
            for (Object o : extractList) {
                Map<String,Object> map = (Map) o;
                for (Map.Entry<String,Object> entry : map.entrySet()) {
                    String key = entry.getKey();
                    if(key.startsWith("header")){
                        saveDatas.put(key,response.getHeader((String)entry.getValue()));
                    }else if(key.startsWith("jsonpath")) {
                        saveDatas.put(key, response.jsonPath().getString((String) entry.getValue()));
                    }else{
                        saveDatas.put(key, response.getBody().jsonPath().getString((String) entry.getValue()));
                    }
                }
            }
        }
    }

    private void doFileUpload(Gson gson, Map requestParams, RequestSpecification rs) throws IOException {
        //RestAssured.config().encoderConfig(encoderConfig().defaultCharsetForContentType("utf-8","multipart/form-data"));
        if(requestParams.get("files") != null){
            List<Map<String,String>> uploadList = (List)requestParams.get("files");
            for(Map<String,String> uploadFile: uploadList){
                rs.multiPart(uploadFile.get("name"),new File(uploadFile.get("file")));
                if("econtract".equals(project)) {
                    requestParams.put(uploadFile.get("name"),
                            DigestUtils.md5Hex(FileUtils.readFileToByteArray(new File(uploadFile.get("file")))));
                }
            }
            requestParams.remove("files");
        }else{
            rs.multiPart(new File((String) requestParams.get("file")));
            requestParams.remove("file");
        }
        Map targetExtMap = (Map) requestParams.get("targetExt");
        if(targetExtMap!=null && targetExtMap.size()>=0){
            String targetExtJson = gson.toJson(targetExtMap);
            requestParams.put("targetExt",targetExtJson);
        }
        digest(requestParams);
        rs.queryParams(requestParams);
//                rs.params(requestParams); //这种方式会乱码?
    }

    private void validate(Response response, ValidatableResponse resp, List<Validate> validateList) {
        for (Validate validate : validateList) {
            String expect = super.getCommonParam(validate.getExpect());
            validate.setExpect(expect);
            if (validate.getCheck().equals(ValidateType.STATUS_CODE)) {
                resp.assertThat().statusCode(Integer.parseInt(validate.getExpect()));
            }else if(validate.getCheck().equals(ValidateType.SCHEMA)){
                resp.assertThat().body(matchesJsonSchemaInClasspath(validate.getExpect()));
            }else  {
                //TODO: 完善常用验证器
                if (validate.getComparator().equals(ValidateType.EQ)) {
                    resp.body(validate.getCheck(), equalTo(validate.getExpect()));
                }else if (validate.getComparator().equals(ValidateType.LEN_EQ)) {
                    resp.body("'" + validate.getCheck() + ".length()'", is(validate.getExpect()));
                }else if (validate.getComparator().equals(ValidateType.IS)) {
                    if ("String".equals(validate.getExpect())) {
                        resp.body(validate.getCheck(), instanceOf(String.class));
                    } else if (TRUE.equals(validate.getExpect())) {
                        resp.body(validate.getCheck(), is(true));
                    } else {
                        resp.body(validate.getCheck(),is(validate.getExpect()));
                    }
                }else if(validate.getComparator().equals(ValidateType.NUM_EQ)){
                    try {
                        resp.body(validate.getCheck(), equalTo(Integer.parseInt(validate.getExpect())));
                    }catch (Exception e){
                        resp.body(validate.getCheck(), is(Float.parseFloat(validate.getExpect())));
                    }
                }else if(validate.getComparator().equals(ValidateType.LT)){
                    resp.body(validate.getCheck(),lessThan(Integer.parseInt(validate.getExpect())));
                }else if(validate.getComparator().equals(ValidateType.LE)){
                    resp.body(validate.getCheck(),lessThanOrEqualTo(Integer.parseInt(validate.getExpect())));
                }else if(validate.getComparator().equals(ValidateType.GT)){
                    try {
                        resp.body(validate.getCheck(),greaterThan(Integer.parseInt(validate.getExpect())));
                    }catch (Exception e){
                        resp.body(validate.getCheck(), greaterThan(Float.parseFloat(validate.getExpect())));
                    }

                }else if(validate.getComparator().equals(ValidateType.GE)){
                    resp.body(validate.getCheck(),greaterThanOrEqualTo(Integer.parseInt(validate.getExpect())));
                }else if(validate.getComparator().equals(ValidateType.NE)){
                    resp.body(validate.getCheck(),not(validate.getExpect()));
                }else if(validate.getComparator().equals(ValidateType.HAS)){
                    String[] expectArray = validate.getExpect().split(",");
                    for(String item :expectArray) {
                        resp.body(validate.getCheck(), hasItems(item));
                    }
                }else if(validate.getComparator().equals("containsString")){
                    resp.body(validate.getCheck(),containsString(validate.getExpect()));
                }else if(validate.getComparator().equals(ValidateType.REGEX)){
                    RegexFunction regexFunction = new RegexFunction();
                    String result = regexFunction.execute(new String[]{validate.getExpect(),
                            response.jsonPath().getString(validate.getCheck())});
                    logger.info("{},{},{}",validate.getExpect(),result,response.jsonPath().getString(validate.getCheck()));
                    Assert.assertTrue(TRUE.equals(result));
                }else if(validate.getComparator().equals(ValidateType.DB)){
                    String execSql = super.getCommonParam(validate.getSql());
                    List result = dao.query(env,execSql);
                    String dbValue = "";
                    if(StringUtil.isNotEmpty(validate.getExpect())){
                        if(result.size()>0){
                            Object[] fieldArray = (Object[]) result.get(0);
                            if(fieldArray.length>0){
                                dbValue = StringUtil.object2String(fieldArray[0]);
                            }
                        }
                        Assert.assertEquals(dbValue,validate.getExpect());
                    }
                }
            }
        }
    }

    private void digest(Map requestParams) {
        String sign = (String) requestParams.get(YamlElements.SIGN);
        if(StringUtil.isNotEmpty(sign)){
            requestParams.remove(YamlElements.SIGN);
            String digestSign = null;
            if("payment".equals(project) || "validate".equals(project)){
                digestSign = DigestUtil.digest(requestParams, sign, DigestUtil.DigestALGEnum.MD5, ENCODING);
            }else if("econtract".equals(project)){
                Map toSignMap = new TreeMap();
                List toSignList = new ArrayList();
                toSignMap.putAll(requestParams);
                List<Map> list = (List) toSignMap.get("list");
                if(list != null) {
                    for (Map data : list) {
                        if (data != null) {
                            TreeMap map = new TreeMap();
                            map.putAll(data);
                            toSignList.add(map);
                        }
                    }
                    toSignMap.put("list", toSignList);
                }
                digestSign = TestRSADigestUtil.digest(toSignMap, sign.replaceAll("\n", ""));
            }else if("dlvapi".equals(project)){
                Map toSignMap = new TreeMap();
                toSignMap.putAll(requestParams);
                Map data = (HashMap) toSignMap.get("data");
                if(data != null) {
                    TreeMap map = new TreeMap();
                    map.putAll(data);
                    toSignMap.put("data", map);
                }
                digestSign = TestRSADigestUtil.digest(toSignMap,sign);
            }
            requestParams.put(YamlElements.SIGN,digestSign);
            logger.info("requestParam sign:{}",requestParams.get("sign"));
        }
    }

    private void beforeAction(Map requestParams) {
        for (Iterator<Map.Entry<String, String>> it = requestParams.entrySet().iterator(); it.hasNext();){
            Map.Entry<String, String> item = it.next();
            if(StringUtil.isEmpty(item.getValue())){
                it.remove();
            }
        }
    }

    private void replacePlaceholder(Map<String,Object> paramMap) {
        if(paramMap == null) return;
        for(Map.Entry<String,Object> entry : paramMap.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            if(value instanceof  String) {
                String param = buildParam((String)value);
                paramMap.put(key, param);
                logger.debug("{},{}",key,param);
            }else if(value instanceof Map){
                logger.info("replacePlaceholder:{}",key);
                replacePlaceholder((Map)value);
            }else if(value instanceof List){
                List<Map> paramList = (List)value;
                if(paramList==null || paramList.size() == 0) continue;
                boolean isDealWithMap = true;
                for (Object obj : paramList) {
                    if(obj instanceof Map) {
                        replacePlaceholder((Map)obj);
                    }else{
                        isDealWithMap = false;
                        break;
                    }
                }
                if(!isDealWithMap){
                    paramMap.put(key, paramList);
                }
            }
        }
    }
}